package fileInput;
import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Random;

import simulator.AsyncBufferCache;
import simulator.BufferCache;
import simulator.FileObject;
import simulator.FileSystem;
import simulator.JournalArea;
import simulator.Policy;
import simulator.SyncBufferCache;
import simulator.SystemSim;
import simulator.UBJCache;

public class EntryPoint {
	
	
	public static ArrayList<FileObject> files1, files2, files3, correctCopy;

	//1: sync, 2: async, 3: UBJ
	//static FileSystem fs1, fs2, fs3;
	
	//static BufferCache bc1, bc2, bc3;
	//static JournalArea ja1, ja2, ja3;
	//static SystemSim ss1, ss2, ss3;
	
	public static FileSystem[] fs = new FileSystem[3];
	public static FileSystem fsCorrect;
	public static BufferCache[] bc = new BufferCache[3];
	public static JournalArea[] ja = new JournalArea[3];
	public static SystemSim[] ss = new SystemSim[3];
	
	public static int numFiles = 100;
	public static int journalSize = 10;
	public static int chkptTimer = 200;
	public static int commitTimer = 60;
	public static int numFrames =100;
	public static int numIterations = 3000;
	public static int SimType = 1;
	
	
	public static void createFiles(ArrayList<FileObject> f)
	{
		//Random rand = new Random();
		
		for(int i = 0; i < numFiles; i++)
		{
			String fName = "f" + i;
			//int data = rand.nextInt(100);
			int data = i;
			
			FileObject file = new FileObject(fName, data);
			f.add(file);
		}
	}
	
	public static void copyFiles(ArrayList<FileObject> f1, ArrayList<FileObject> f2)
	{
		for(int i = 0; i < f1.size(); i++)
		{
			FileObject file1 = f1.get(i);
			FileObject file1_copy = new FileObject(file1.getName(), file1.getData());
			f2.add(file1_copy);
		}
	}
	
	public static void SetupSystems()
	{
		for(int i = 0; i < 3; i++)
		{
			fs[i].setsystem(ss[i], ja[i], bc[i]);
			ja[i].setsystem(fs[i], ss[i], bc[i]);
			bc[i].setsystem(fs[i], ss[i], ja[i]);
		}
	}
	

	public static void main(String[] args) throws IOException {
		
		System.out.println("Please type in the parameters with space in between:" 
				+ "\nNumFiles journalSize commitTimer checkpointTimer numFrames numCommandsLoop" +
				"\nOr type 'exit' to exit.");
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	    String s = br.readLine();
		
		String[] input = s.split(" ");
		
		if(input.length != 6) {
			System.out.println("Incorrect number of parameters!");
			return;
		}
		
		
		parseInput(input);
		
		SetupSims();
	}
	
	public static void parseInput(String[] input)
	{
		numFiles = Integer.parseInt(input[0]);
		journalSize = Integer.parseInt(input[1]);
		commitTimer = Integer.parseInt(input[2]);
		chkptTimer = Integer.parseInt(input[3]);
		numFrames = Integer.parseInt(input[4]);
		numIterations = Integer.parseInt(input[5]);
	}
	
	
	
	
	public static void SetupSims()
	{
		files1 = new ArrayList<FileObject>();
		files2 = new ArrayList<FileObject>();
		files3 = new ArrayList<FileObject>();
		correctCopy = new ArrayList<FileObject>();
		// create 100 files: f1 to f100
		createFiles(files1);
		copyFiles(files1, files2);
		copyFiles(files2, files3);
		copyFiles(files3, correctCopy);
		
		fs[0] = new FileSystem(files1);
		fs[1] = new FileSystem(files2);
		fs[2] = new FileSystem(files3);
		fsCorrect = new FileSystem(correctCopy);
		
		bc[0] = new SyncBufferCache(numFrames);
		bc[1] = new AsyncBufferCache(numFrames);
		bc[2] = new UBJCache(numFrames);
		
		ja[0] = new JournalArea(journalSize);
		ja[1] = new JournalArea(journalSize);
		ja[2] = new JournalArea(journalSize);

		
		ss[0] = new SystemSim(commitTimer, chkptTimer, new Policy(Policy.Sync), fs[0], ja[0], bc[0]);
		ss[1] = new SystemSim(commitTimer, chkptTimer, new Policy(Policy.Async), fs[1], ja[1], bc[1]);
		ss[2] = new SystemSim(commitTimer, chkptTimer, new Policy(Policy.UBJ), fs[2], ja[2], bc[2]);
		
		SetupSystems();
		
		if (SimType == 1) 
			SimRandom(numIterations);
		else
			SimSeq(numIterations);
	}
	
	/**
	 * Input a series of commands that operate sequentially on the file system
	 * First, a read command is generated for a file.  Then a series of writes occur on that file.
	 * After this, the very next file is read, and the process continues for an input number of iterations
	 * @param numIterations: The number of iterations of the sequence described above
	 */
	private static void SimSeq(int numIterations)
	{
		int consecutiveWrites = 5;
		int totalCommands = 5 * numIterations;
		
		int totalReads, totalWrites;
		totalReads = totalWrites = 0;
		int[] dataValues = new int[3];
		for(int i = 0; i < numIterations; i++)
		{
			String fileName = "f" + getRandomIndex(0, numFiles - 1);
			//String fileName = "f" + i % files1.size();
			int x;
			if(fileName.equals("f4"))
				x = 5;
			
			totalReads++;
			FileObject fo = fsCorrect.getFile(fileName);
			int initC = fo.getData();
			
			
			for(int k =0; k < 3; k++)
			{
				//System.out.println("Reading File name: " + fileName + "\tFile Data: " + bc[j].readBlock(fileName));
				dataValues[k] = bc[k].readBlock(fileName);
				ss[k].UpdateTime();
			}
			
			for(int j = 1; j <= consecutiveWrites; j++){
				totalWrites++;
				
				int correctValue = initC + j * 10;
				
				for(int k =0; k < 3; k++)
				{
					//System.out.println("Reading File name: " + fileName + "\tFile Data: " + bc[j].readBlock(fileName));
					if(k == 2 && j == 3 && i == 91)
						x = 6;
					
				   int val =  dataValues[k] + j * 10;
					
					bc[k].updateBlock(fileName, val);
					ss[k].UpdateTime();
				}
				
				
				
				fsCorrect.updateFile(fileName, initC + j * 10);
			}
		}
		
		//Make sure to commit final data to file system
				for(int i = 0; i < 3; i++)
					bc[i].flushDirty();
				
				System.out.println("Total Reads: " + totalReads);
				System.out.println("Total Writes: " + totalWrites);
		
		
		PrintResults();
	}
	
	
	/**
	 * Simulate a random series of commands.
	 * The file to be updated, the command (read/write), and the update value (on a write) are completely randomized
	 * @param numCommands
	 */
	private static void SimRandom(int numCommands)
	{
		int totalReads, totalWrites;
		totalReads = totalWrites = 0;
		
		for(int i = 0; i < numCommands; i++)
		{
			// Generate part of fileName, range: 0~99
			String fileName = "f" + getRandomIndex(0, numFiles - 1);
			//System.out.println("Iteration number " + i);
			
			// Event is read block, else update block
			if(eventIsRead())
			{
				totalReads++;
				for(int j =0; j < 3; j++)
				{
					//System.out.println("Reading File name: " + fileName + "\tFile Data: " + bc[j].readBlock(fileName));
					bc[j].readBlock(fileName);
					ss[j].UpdateTime();
				}
			}
			else
			{
				totalWrites++;
				int randomInt = getRandomIndex(1, 500);
				for(int j =0; j < 3; j++)
				{
					//System.out.println("Writing to file: " + fileName + "\tNew Data: " + randomInt);
					bc[j].updateBlock(fileName, randomInt);
					ss[j].UpdateTime();
				}
				
				fsCorrect.updateFile(fileName, randomInt);
			}

		}
		
		//Make sure to commit final data to file system
		for(int i = 0; i < 3; i++)
			bc[i].flushDirty();
		
		System.out.println("Total Reads: " + totalReads);
		System.out.println("Total Writes: " + totalWrites);
		
		PrintResults();
	}
	
	private static void PrintResults()
	{
		if(fileNameCheck() == false)
			System.out.println("Inconsistent names!");
		
		//printFileContents();
		PrintDiff();
		PrintStats();
	}
	
	//Print the stats for each simulation
	private static void PrintStats()
	{
		for(int i = 0; i < 3; i++)
		{
			System.out.println("----------------------------------------------------");
			System.out.println(ss[i].simString() + "\n");
		}
	}
	
	//Print the number of incorrect files in the file system for each simulation
	private static void PrintDiff()
	{
		int syncCounter, asyncCounter, ubjCounter;
		syncCounter = asyncCounter = ubjCounter = 0;
		
		for(int i = 0; i < files1.size();i++)
		{
			if(files1.get(i).getData() != correctCopy.get(i).getData())
				syncCounter++;
			if(files2.get(i).getData() != correctCopy.get(i).getData())
				asyncCounter++;
			if(files3.get(i).getData() != correctCopy.get(i).getData())
				ubjCounter++;
		}
		System.out.println();
		System.out.println("Wrong files in sync: " + syncCounter + "\tWrong files in async: " + asyncCounter + "\tWrong files in UBJ: " + ubjCounter);
		System.out.println();
	}
	
	//Make sure that there has been no corruption to file names
	private static boolean fileNameCheck()
	{
		for(int i = 0; i < files1.size(); i++)
		{
			FileObject fo1 = files1.get(i);
			FileObject fo2 = files2.get(i);
			FileObject fo3 = files3.get(i);
			
			if(fo1.getName() != fo2.getName() || fo1.getName() != fo3.getName() || fo2.getName() != fo3.getName())
				return false;
		}
		
		return true;
	}

	//Print the contents of the file system for each policy after a simulation
	private static void printFileContents()
	{
		for(int i = 0; i < files1.size();i++)
		{
			System.out.println(""
					+ files1.get(i).getName() + ":"
					+ "\t" + files1.get(i).getData()
					+ "\t" + files2.get(i).getData() 
					+ "\t" + files3.get(i).getData()
					+ "\t" + correctCopy.get(i).getData()
					);
		}
	}
	
	
	private static int getRandomIndex(int min, int max)
	{
		return min + (int)(Math.random() * ((max - min) + 1));
	}
	
	private static boolean eventIsRead()
	{
		return Math.random() < 0.5;
	}
}
